package com.ww.microrpc.executer.impl;

import com.ww.microrpc.executer.MethodExecutors;
import com.ww.microrpc.executer.RpcInvoke;
import com.ww.microrpc.executer.exception.NotFoundClassOrMethodException;
import com.ww.microrpc.netty.protocol.RequestMessage;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;

import java.beans.Introspector;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

public class BeanMethodExecutors implements MethodExecutors, ApplicationContextAware {

    public static final String INVOKE_ALL_METHOD = "all";
    public static final String INVOKE_MARKED_METHOD = "marked";
    ApplicationContext applicationContext;

    @Value("${server.invoke.allowed:all}")
    private String invokeAllowed;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }

    @Override
    public Object invoke(RequestMessage request) {
        try {
            String className = request.getClassName();
            String methodName = request.getMethodName();
            Object[] parameters = request.getParameters();
            Class<?>[] parameterTypes = request.getParameterTypes();

            String beanName = getDefaultBeanName(className);

            Object bean = applicationContext.getBean(beanName);
            Method method = bean.getClass().getMethod(methodName, parameterTypes);

//        FastClass serviceFastClass = FastClass.create(bean.getClass());
//        FastMethod method = serviceFastClass.getMethod(methodName, parameterTypes);

            boolean isAllowed = false;
            if (INVOKE_ALL_METHOD.equals(invokeAllowed)) {
                isAllowed = true;
            } else if (INVOKE_MARKED_METHOD.equals(invokeAllowed)
                    && (bean.getClass().getAnnotation(RpcInvoke.class) != null
                    || method.getAnnotation(RpcInvoke.class) != null)
            ) {
                isAllowed = true;
            }

            if (isAllowed) {
                return method.invoke(bean, parameters);
            } else {
                throw new NotFoundClassOrMethodException();
            }
        } catch (InvocationTargetException | NoSuchMethodException | IllegalAccessException e) {
            throw new RuntimeException(e);
        }
    }

    private String getDefaultBeanName(String className) {
        Assert.state(className != null, "no Bean class name set");
        String shortName = ClassUtils.getShortName(className);
        return Introspector.decapitalize(shortName);
    }
}
